﻿//	Copyright (c) 2012 Jonathan Loe
//
//	MIT license (http://en.wikipedia.org/wiki/MIT_License)
//
//	Permission is hereby granted, free of charge, to any person obtaining a copy
//	of this software and associated documentation files (the "Software"), to deal
//	in the Software without restriction, including without limitation the rights 
//	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
//	of the Software, and to permit persons to whom the Software is furnished to do so, 
//	subject to the following conditions:
//
//	The above copyright notice and this permission notice shall be included in all 
//	copies or substantial portions of the Software.
//
//	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 
//	INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
//	PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 
//	FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
//	ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Data.Linq;
using System.Data.Linq.Mapping;
using System.Linq;
using System.Reflection;
using System.Runtime.Remoting;
using System.Text;
using System.Text.RegularExpressions;

//using Ordinaire.Taxonomy.Data;

namespace Ordinaire
{
    /// <summary>
    /// Holds collection of extension methods relating to general types such as int, string, string[], etc.
    /// </summary>
    public static class ExtensionMethods
    {
        #region Helper

        /// <summary>
        /// Returns some info on ChangeConflictException.
        /// </summary>
        /// <typeparam name="TEntity">entity suspected to cause conflict</typeparam>
        /// <param name="context">context to test</param>
        /// <returns>info text</returns>
        /// <remarks>use this to help troubleshoot ConflictException</remarks>
        public static string ChangeConflictInfo<TEntity>(this DataContext context)
            where TEntity : class
        {
            StringBuilder strError = new StringBuilder();

            foreach (ObjectChangeConflict changeConflict in context.ChangeConflicts)
            {
                MetaTable metatable = context.Mapping.GetTable(changeConflict.Object.GetType());
                TEntity entityInConflict = (TEntity) changeConflict.Object;

                strError.AppendFormat("Table name: {0}", metatable.TableName);
                strError.AppendLine();

                foreach (MemberChangeConflict col in changeConflict.MemberConflicts)
                {
                    strError.AppendFormat("Column name : {0}", col.Member.Name);
                    strError.AppendLine();
                    strError.AppendFormat("Original value : {0}", col.OriginalValue.ToString());
                    strError.AppendLine();
                    strError.AppendFormat("Current value : {0}", col.CurrentValue.ToString());
                    strError.AppendLine();
                    strError.AppendFormat("Database value : {0}", col.DatabaseValue.ToString());
                    strError.AppendLine();
                }
            }

            return strError.ToString();
        }

        /// <summary>
        /// Returns the data context used by this query.
        /// </summary>
        /// <param name="query">the query</param>
        /// <returns>DataContext instance; otherwise null</returns>
        public static DataContext DataContext(this IQueryable query)
        {
            Type queryType = query.GetType();

            if (!queryType.FullName.StartsWith("System.Data.Linq.DataQuery"))
            {
                return null;
            }

            var field = queryType.GetField("context", BindingFlags.NonPublic | BindingFlags.Instance);
            if (field == null)
            {
                return null;
            }

            return field.GetValue(query) as DataContext;
        }

        /// <summary>
        /// Returns the corresponding command text of the specified query.
        /// </summary>
        /// <param name="query">the query to be translated</param>
        /// <param name="context">the DataContext used by the query</param>
        /// <returns>the corresponding command text of the query</returns>
        public static string ToCommandText(this IQueryable query, DataContext context = null)
        {
            // Attempt to get DataContext, if not specified
            if (context == null)
            {
                context = query.DataContext();
            }

            if (context == null)
            {
                throw new OException("DataContext cannot be found");
            }

            return context.GetCommand(query).CommandText;
        }

        #endregion

        #region Reflection

        /// <summary>
        /// Returns the column attribute of specified instance field.
        /// </summary>
        /// <param name="instance">current object</param>
        /// <param name="name">field name</param>
        /// <returns>column attribute name</returns>
        public static T Attribute<T>(this object instance, string name = null)
        {
            object[] info;
            Type type = instance.GetType();
            if (String.IsNullOrEmpty(name))
            {
                info = type.GetCustomAttributes(typeof(T), false);
            }
            else
            {
                PropertyInfo pi = type.GetProperty(name);
                info            = pi.GetCustomAttributes(typeof(T), true);
            }

            if (info != null && info.Length > 0)
            {
                return (T)info[0];
            }

            // Throw exception because null cannot be returned as T.
            object[] exParams = new object[] {
                typeof(T).FullName,
                instance.GetType().FullName
            };
            throw ExceptionFactory.Create<AttributeException>(ExceptionMessage.NoMatchingAttribute, exParams);
        }

        /// <summary>
        /// Returns the name value of specified attribute of the specified instance.
        /// </summary>
        /// <param name="instance">current object</param>
        /// <param name="field">property name if need be</param>
        /// <returns>name of TableAttribute, else if none is specified, class name is used</returns>
        public static string AttributeName<T>(this object instance, string field = null)
        {
            try
            {
                if (typeof(T) == typeof(TableAttribute))
                {
                    TableAttribute attribute = instance.Attribute<TableAttribute>();
                    string name = attribute.Name;
                    return (String.IsNullOrEmpty(name)) ? instance.GetType().Name : name;
                }
                else if (typeof(T) == typeof(ColumnAttribute))
                {
                    ColumnAttribute attribute = instance.Attribute<ColumnAttribute>(field);
                    string name = attribute.Name;
                    return (String.IsNullOrEmpty(name)) ? field : name;
                }

                throw ExceptionFactory.Create<TypeNotSupportedException>(ExceptionMessage.TypeNotSupported, typeof(T).FullName);
            }
            catch (AttributeException)
            {
                return String.Empty;
            }
        }

        /// <summary>
        /// Returns new instance of specified type with specified arguments.
        /// </summary>
        /// <param name="type">the type of instance to create</param>
        /// <param name="args">an array of arguments that match in number, order, and type the parameters of constructor to invoke</param>
        /// <returns>new instance of specified type</returns>
        public static object Instance(this Type type, params object[] args)
        {
            return Activator.CreateInstance(type, args);
        }

        /// <summary>
        /// Returns new instance with specified type and assembly names.
        /// </summary>
        /// <param name="typeName">the name of preferred type</param>
        /// <param name="assemblyName">the name of assembly</param>
        /// <returns>new instance of the preferred type name</returns>
        public static object Instance(this string typeName, string assemblyName)
        {
            return Activator.CreateInstance(Type.GetType(assemblyName));
        }

        /// <summary>
        /// Returns a new instance of the specified type with specified arguments.
        /// </summary>
        /// <typeparam name="T">strongly type instance</typeparam>
        /// <param name="type">type of the new instance</param>
        /// <param name="args">values to be passed as arguments</param>
        /// <returns>new instance of the specified type</returns>
        public static T Instance<T>(this Type type, params object[] args)
        {
            return (T) type.Instance(args);
        }

        /// <summary>
        /// Returns a new instance of the specified type and assembly names.
        /// </summary>
        /// <typeparam name="T">the name of preferred returned type</typeparam>
        /// <param name="typeName">the name of preferred type</param>
        /// <param name="assemblyName">the name of assembly</param>
        /// <returns>new instance of the preferred type name</returns>
        public static T Instance<T>(this string typeName, string assemblyName)
        {
            return (T) typeName.Instance(assemblyName);
        }

        /// <summary>
        /// Returns the name of current enum.
        /// </summary>
        /// <param name="instance">the enum value</param>
        /// <returns>the string representation of specified enum value</returns>
        public static string Name(this Enum instance)
        {
            return Enum.GetName(instance.GetType(), instance);
        }

        /// <summary>
        /// Invokes method name of the specified instance with the given arguments/parameters.
        /// </summary>
        /// <typeparam name="TEntity">type of the instance</typeparam>
        /// <param name="instance">the instance to execute the method</param>
        /// <param name="methodName">the method name to execute</param>
        /// <param name="args">the parameters to use</param>
        public static void Invoke<TEntity>(this TEntity instance, string methodName, params object[] args)
            where TEntity : class
        {
            instance.ThrowIfNull<ArgumentNullException>("instance");

            var methodInfo = instance.GetType().GetMethod(methodName);
            methodInfo.ThrowIfNull<ArgumentException>(String.Format(ExceptionMessage.Invalid, String.Format("method name '{0}'", methodName)));
            methodInfo.Invoke(instance, args);
        }

        /// <summary>
        /// Iterates and invokes the method of each instance with the given arguments/parameters.
        /// </summary>
        /// <typeparam name="TEntity">type of instance</typeparam>
        /// <param name="list">the list of instances to execute the method</param>
        /// <param name="methodName">the method name</param>
        /// <param name="args">the parameters to use</param>
        public static void InvokeEach<TEntity>(this IEnumerable<TEntity> list, string methodName, params object[] args)
            where TEntity : class
        {
            list.ThrowIfNullOrEmpty<ArgumentException>(String.Format(ExceptionMessage.IsNullOrEmpty, "list"));

            foreach (object entity in list)
            {
                entity.Invoke(methodName, args);
            }
        }

        #endregion

        #region Verification

        /// <summary>
        /// Returns a value indicating whether values in the array contains null or empty string.
        /// </summary>
        /// <param name="array">array to check</param>
        /// <returns>true if the null or empty occurs within this array; otherwise false</returns>
        public static bool ContainsNullOrEmpty(this string[] array)
        {
            if (array == null || array.Length == 0)
            {
                return true;
            }

            foreach (string item in array)
            {
                if (String.IsNullOrEmpty(item))
                {
                    return true;
                }
            }

            return false;
        }

        #endregion

        #region Exception

        /// <summary>
        /// Throws new exception of specified type with the specified parameters if current instance contains null value.
        /// </summary>
        /// <typeparam name="TException">the type of exception to throw</typeparam>
        /// <param name="instances">instances to test</param>
        /// <param name="args">an array of arguments that match with the parameters of the type constructor to invoke</param>
        public static void ThrowIfContainsNull<TException>(this object[] instances, params object[] args)
            where TException : Exception
        {
            if (instances == null)
            {
                throw typeof(TException).Instance<TException>(args);
            }

            for (int i = 0; i < instances.Length; i++)
            {
                instances[i].ThrowIfNull<TException>(args);
            }
        }

        /// <summary>
        /// Throws new exception of specified type with the specified parameters if current instance contains empty value.
        /// </summary>
        /// <typeparam name="TException">the type of exception to throw</typeparam>
        /// <param name="instances">instances to test</param>
        /// <param name="args">an array of arguments that match with the parameters of the type constructor to invoke</param>
        public static void ThrowIfContainsEmpty<TException>(this object[] instances, params object[] args)
            where TException : Exception
        {
            if (instances == null)
            {
                throw typeof(TException).Instance<TException>(args);
            }

            for (int i = 0; i < instances.Length; i++)
            {
                instances[i].ThrowIfEmpty<TException>(args);
            }
        }

        /// <summary>
        /// Throws new exception of specified type with the specified parameters if current instance contains null or empty value.
        /// </summary>
        /// <typeparam name="TException">the type of exception to throw</typeparam>
        /// <param name="instances">instances to test</param>
        /// <param name="args">an array of arguments that match with the parameters of the type constructor to invoke</param>
        public static void ThrowIfContainsNullOrEmpty<TException>(this object[] instances, params object[] args)
            where TException : Exception
        {
            if (instances == null)
            {
                throw typeof(TException).Instance<TException>(args);
            }

            for (int i = 0; i < instances.Length; i++)
            {
                instances[i].ThrowIfNullOrEmpty<TException>(args);
            }
        }

        /// <summary>
        /// Throws new exception of specified type with the specified parameters if current instance is null.
        /// </summary>
        /// <typeparam name="TException">the type of exception to throw</typeparam>
        /// <param name="instances">instance to test</param>
        /// <param name="args">an array of arguments that match with the parameters of the type constructor to invoke</param>
        public static void ThrowIfNull<TException>(this object[] instances, params object[] args)
            where TException : Exception
        {
            if (instances == null)
            {
                throw typeof(TException).Instance<TException>(args);
            }
        }

        /// <summary>
        /// Throws new exception of specified type with the specified parameters if current instance is empty.
        /// </summary>
        /// <typeparam name="TException">the type of exception to throw</typeparam>
        /// <param name="instances">instance to test</param>
        /// <param name="args">an array of arguments that match with the parameters of the type constructor to invoke</param>
        public static void ThrowIfEmpty<TException>(this object[] instances, params object[] args)
            where TException : Exception
        {
            if (instances.Length == 0)
            {
                throw typeof(TException).Instance<TException>(args);
            }
        }

        /// <summary>
        /// Throws new exception of specified type if current name already exists that is indicated by the specified dataManager call.
        /// </summary>
        /// <typeparam name="TException">the type of exception to throw</typeparam>
        /// <param name="instance">instance to test</param>
        /// <param name="dataManager">the class that implements Ordinaire.IExist</param>
        /// <param name="whatExist">the text indicating what already exists as part of the exception message</param>
        public static void ThrowIfExistsByName<TException>(this string instance, IExist dataManager, string whatExist)
            where TException : Exception
        {
            if (instance != null && dataManager != null && dataManager.ExistsByName(instance))
            {
                throw ExceptionFactory.Create<TException>(ExceptionMessage.AlreadyExists, whatExist);
            }
        }

        /// <summary>
        /// Throws new exception of specified type if current instance does not exists that is indicated by the specified dataManager call.
        /// </summary>
        /// <typeparam name="TException">the type of exception to throw</typeparam>
        /// <typeparam name="TId">the type of unique identifier</typeparam>
        /// <param name="instance">instance to test</param>
        /// <param name="dataManager">the class that implements Ordinaire.IExist</param>
        /// <param name="whatNotExist">the text indicating what does not exists as part of the exception message</param>
        public static void ThrowIfNotExists<TException, TId>(this TId instance, IExist dataManager, string whatNotExist)
            where TException : Exception
        {
            if (instance != null && dataManager != null && !dataManager.Exists<TId>(instance))
            {
                throw ExceptionFactory.Create<TException>(ExceptionMessage.NotExists, whatNotExist);
            }
        }

        /// <summary>
        /// Throws new exception of specified type if current name does not exists that is indicated by the specified dataManager call.
        /// </summary>
        /// <typeparam name="TException">the type of exception to throw</typeparam>
        /// <param name="instance">instance to test</param>
        /// <param name="dataManager">the class that implements Ordinaire.IExist</param>
        /// <param name="whatNotExist">the text indicating what does not exists as part of the exception message</param>
        public static void ThrowIfNotExistsByName<TException>(this string instance, IExist dataManager, string whatNotExist)
            where TException : Exception
        {
            if (instance != null && dataManager != null && !dataManager.ExistsByName(instance))
            {
                throw ExceptionFactory.Create<TException>(ExceptionMessage.NotExists, whatNotExist);
            }
        }

        /// <summary>
        /// Throws new exception of specified type with the specified parameters if current instance is null or empty.
        /// </summary>
        /// <typeparam name="TException">the type of exception to throw</typeparam>
        /// <param name="instances">instance to test</param>
        /// <param name="args">an array of arguments that match with the parameters of the type constructor to invoke</param>
        public static void ThrowIfNullOrEmpty<TException>(this object[] instances, params object[] args)
            where TException : Exception
        {
            instances.ThrowIfNull<TException>(args);
            instances.ThrowIfEmpty<TException>(args);
        }

        /// <summary>
        /// Throws new exception of specified type with the specified parameters if current instance is null.
        /// </summary>
        /// <typeparam name="TException">the type of exception to throw</typeparam>
        /// <param name="instance">instance to test</param>
        /// <param name="args">an array of arguments that match with the parameters of the type constructor to invoke</param>
        public static void ThrowIfNull<TException>(this object instance, params object[] args)
            where TException : Exception
        {
            if (instance == null)
            {
                throw typeof(TException).Instance<TException>(args);
            }
        }

        /// <summary>
        /// Throws new exception of specified type with the specified parameters if current instance is empty.
        /// For type integer, empty refers to a zero value '0'. 
        /// </summary>
        /// <typeparam name="TException">the type of exception to throw</typeparam>
        /// <param name="instance">instance to test</param>
        /// <param name="args">an array of arguments that match with the parameters of the type constructor to invoke</param>
        public static void ThrowIfEmpty<TException>(this object instance, params object[] args)
            where TException : Exception
        {
            Type instanceType = instance.GetType();
            if (instance is IList && instance.GetType().IsGenericType)
            {
                IList list = (IList) instance;
                if (list.Count == 0)
                {
                    throw typeof(TException).Instance<TException>(args);
                }

                return;
            }
            else if (instance.GetType().IsArray)
            {
                Array arr = (Array) instance;
                if (arr.Length == 0)
                {
                    throw typeof(TException).Instance<TException>(args);
                }

                return;
            }
            else if (instanceType == typeof(string))
            {
                string text = (string)instance;
                if (text.Trim() == String.Empty)
                {
                    throw typeof(TException).Instance<TException>(args);
                }

                return;
            }
            else if (instanceType == typeof(int))
            {
                int integer = (int)instance;
                if (integer == 0)
                {
                    throw typeof(TException).Instance<TException>(args);
                }

                return;
            }
            else if (instanceType == typeof(decimal))
            {
                decimal dec = (decimal)instance;
                if (dec == (decimal) 0)
                {
                    throw typeof(TException).Instance<TException>(args);
                }

                return;
            }

            throw ExceptionFactory.Create<NotImplementedException>(ExceptionMessage.TypeNotSupported, instanceType.FullName);
        }

        /// <summary>
        /// Throws new exception of specified type with the specified parameters if current instance is null or empty.
        /// </summary>
        /// <typeparam name="TException">the type of exception to throw</typeparam>
        /// <param name="instance">instance to test</param>
        /// <param name="args">an array of arguments that match with the parameters of the type constructor to invoke</param>
        public static void ThrowIfNullOrEmpty<TException>(this object instance, params object[] args)
            where TException : Exception
        {
            instance.ThrowIfNull<TException>(args);
            instance.ThrowIfEmpty<TException>(args);
        }

        #endregion

        #region Conversion

        /// <summary>
        /// Returns new instance of the specified Enum.
        /// </summary>
        /// <param name="text">string representation of an Enum value</param>
        /// <returns>the corresponding Enum</returns>
        public static T ToEnum<T>(this string text)
        {
            Type   type      = typeof(T);
            string exMessage = String.Empty;

            if (!type.IsEnum)
            {
                throw ExceptionFactory.Create<ArgumentException>(ExceptionMessage.InvalidEnumType, type.FullName);
            }

            text = text.ToLower();
            foreach (object myEnum in Enum.GetValues(typeof(T)))
            {
                if (myEnum.ToString().ToLower() == text)
                {
                    return (T) myEnum;
                }
            }

            throw ExceptionFactory.Create<ArgumentException>(ExceptionMessage.InvalidEnumValue, new object[] { text, type.FullName });
        }

        /// <summary>
        /// Converts current object to boolean or returns false or specified default value if current value is DBNull.
        /// </summary>
        /// <param name="value">value</param>
        /// <returns>returns false if value is of DBNull type.</returns>
        public static bool ToBooleanOrDefault(this object value, bool defaultValue = false)
        {
            if (value is DBNull)
            {
                return defaultValue;
            }

            return Convert.ToBoolean(value);
        }

        /// <summary>
        /// Converts specified instance to DataSet instance.
        /// </summary>
        /// <typeparam name="TEntity">type of entity to convert</typeparam>
        /// <param name="instance">entity instance to convert</param>
        /// <returns>a DataSet instance</returns>
        public static DataSet ToDataSet<TEntity>(this TEntity instance)
            where TEntity : class
        {
            DataSet dataSet = new DataSet();

            if (instance == null)
            {
                return dataSet;
            }

            Type entityType = typeof(TEntity);
            Type listType   = typeof(List<>);
            if (entityType.IsGenericTypeDefinition && entityType.GetGenericTypeDefinition() == listType)
            {
                throw new OException(String.Format("{0} is not supported yet", listType.FullName));
            }

            string    columnName;
            List<int> propIndexes = new List<int>(); // used to keep track of indexes to increase performance when populating values
            DataTable table       = new DataTable(entityType.Name);

            /* Generate columns */
            IList<PropertyInfo> propertyInfos = entityType.GetProperties().ToList();
            int propertyInfosCount = propertyInfos.Count;
            for (int i = 0; i < propertyInfosCount; i++)
            {
                columnName = String.Empty;

                PropertyInfo propertyInfo = propertyInfos[i];
                ColumnAttribute[] columnAttributes = (ColumnAttribute[]) propertyInfo.GetCustomAttributes(typeof(ColumnAttribute), false);
                if (columnAttributes.Length > 0)
                {
                    ColumnAttribute columnAttribute = columnAttributes[0];
                    columnName = columnAttribute.Name;

                    if (String.IsNullOrEmpty(columnName))
                    {
                        columnName = propertyInfo.Name;
                    }

                    table.Columns.Add(columnName, Nullable.GetUnderlyingType(propertyInfo.PropertyType) ?? propertyInfo.PropertyType);
                    propIndexes.Add(i);
                }
            }

            table.Rows.Add(table.NewRow());

            /* Populate values */
            int index = 0;
            int propIndexesCount = propIndexes.Count;
            for (int i = 0; i < propIndexesCount; i++)
            {
                index = propIndexes[i];
                PropertyInfo propertyInfo = propertyInfos[index];

                ColumnAttribute[] columnAttributes = (ColumnAttribute[]) propertyInfo.GetCustomAttributes(typeof(ColumnAttribute), false);
                if (columnAttributes.Length > 0)
                {
                    ColumnAttribute columnAttribute = columnAttributes[0];
                    columnName = columnAttribute.Name;

                    if (String.IsNullOrEmpty(columnName))
                    {
                        columnName = propertyInfo.Name;
                    }

                    table.Rows[0][columnName] = propertyInfo.GetValue(instance, null) ?? DBNull.Value;
                }
            }

            dataSet.Tables.Add(table);
            return dataSet;
        }
		
        /// <summary>
        /// Converts current object to System.DateTime or returns specified default value if value is DBNull.
        /// </summary>
        /// <param name="value">value</param>
        /// <returns>returns DateTime.Ticks = 0 if value is of DBNull type.</returns>
        public static DateTime ToDateTimeOrDefault(this object value, long ticks = (long) 0)
        {
            if (value is DBNull)
            {
                return new DateTime(ticks);
            }

            return Convert.ToDateTime(value);
        }

        /// <summary>
        /// Converts current object to decimal or returns specified default value if value is DBNull.
        /// </summary>
        /// <param name="value">value</param>
        /// <returns>returns 0 if value is of DBNull type.</returns>
        public static decimal ToDecimalOrDefault(this object value, decimal defaultValue = (decimal) 0)
        {
            if (value is DBNull)
            {
                return defaultValue;
            }

            return Convert.ToDecimal(value);
        }

        /// <summary>
        /// Converts the entity property(ies) to list of key value pair(s) where key represents the property name.
        /// </summary>
        /// <typeparam name="TEntity">the type of entity</typeparam>
        /// <param name="value">entity instance</param>
        /// <returns>list of key value pairs as Dictionary</returns>
        public static Dictionary<string, object> ToDictionary<TEntity>(this TEntity value)
            where TEntity : class
        {
            Dictionary<string, object> result = new Dictionary<string,object>();

            var properties       = typeof(TEntity).GetProperties();
            var propertiesLength = properties.Length;
            for (int i = 0; i < propertiesLength; i++)
            {
                var property = properties[i];
                result.Add(property.Name, property.GetValue(value, null));
            }

            return result;
        }

        /// <summary>
        /// Converts current object to int or returns false or specified default value if current value is DBNull.
        /// </summary>
        /// <param name="value">value</param>
        /// <returns>returns 0 if value is of DBNull type.</returns>
        public static int ToInt32OrDefault(this object value, int defaultValue = 0)
        {
            if (value is DBNull)
            {
                return defaultValue;
            }

            return Convert.ToInt32(value);
        }

        /// <summary>
        /// Convert current object to string or returns specified default value if value is DBNull.
        /// </summary>
        /// <param name="value">value</param>
        /// <returns>returns String.Empty if value is of DBNull type.</returns>
        public static String ToStringOrDefault(this object value, string defaultValue = "")
        {
            if (value is DBNull)
            {
                return defaultValue;
            }

            return Convert.ToString(value);
        }

        #endregion

        #region Text Manipulation

        /// <summary>
        /// Uppercase the first letter of the specified text.
        /// </summary>
        /// <param name="text">current text</param>
        /// <returns>new text with first letter upper-cased</returns>
        public static string CapitaliseFirstLetter(this string text)
        {
            text = text.TrimStart();
            if (String.IsNullOrEmpty(text))
            {
                return text;
            }
            text = text.ToLower();

            char[] chars = text.ToCharArray();
            chars[0] = char.ToUpper(chars[0]);
            return String.Join(String.Empty, chars);
        }

        /// <summary>
        /// Concatenates all elements of string, using the specified separator between each element.
        /// </summary>
        /// <param name="array">array of string</param>
        /// <param name="separator">The string to use as separator</param>
        /// <returns>the joined string</returns>
        public static string Join(this string[] array, string separator)
        {
            return String.Join(separator, array);
        }

        /// <summary>
        /// Returns a text with space or a specified separator in between camel-cased word.
        /// </summary>
        /// <param name="text">camel case text</param>
        /// <param name="separator">separator</param>
        /// <returns>space-separated text</returns>
        public static string SplitCamelCase(this string text, string separator = " ")
        {
            string tmp = Regex.Replace(text, "([A-Z])", separator + "$1", RegexOptions.Compiled);
            return (tmp == null) ? String.Empty : tmp.Trim();
        }

        /// <summary>
        /// Takes the last string in the array that is delimited by specified delimiter.
        /// </summary>
        /// <param name="text">text input</param>
        /// <param name="delimiter">the delimiter</param>
        /// <returns>the last string in the array</returns>
        public static string TakeLastBy(this string text, string delimiter)
        {
            delimiter.ThrowIfNullOrEmpty<ArgumentException>("delimiter is null or empty");

            if (String.IsNullOrEmpty(text))
            {
                return String.Empty;
            }

            string[] tokens = text.Split(delimiter.ToCharArray());
            return tokens[tokens.Length - 1];
        }

        /// <summary>
        /// Lowercase string in the specified array.
        /// </summary>
        /// <param name="array">an array of string</param>
        /// <returns>an array of lowercased string</returns>
        public static string[] ToLower(this string[] array)
        {
            List<string> result = new List<string>();

            for (int i = 0; i < array.Length; i++)
            {
                result.Add(array[i].ToLower());
            }

            return result.ToArray();
        }

        /// <summary>
        /// Splits the specified fullname into several names: first, middle, and last.
        /// </summary>
        /// <param name="fullname">fullname</param>
        /// <returns>an array of name(s): first, middle, and last</returns>
        /// <remarks>Person name will be broken into three names: first as first name, last as last name, and the rest as middle name(s).</remarks>
        public static string[] ToNames(this string fullname)
        {
            if (String.IsNullOrEmpty(fullname))
            {
                return new string[0];
            }

            string[] names = new string[3];

            string[] tokens = fullname.Split(new char[] { ' ' });
            if (tokens.Length > 0)
            {
                // take first token as firstname
                names[0] = tokens[0];

                // take last one as lastname
                if (tokens.Length > 1)
                {
                    names[2] = tokens[tokens.Length - 1];
                }

                // take the rest as middle name(s)
                if (tokens.Length > 2)
                {
                    List<string> nameList = tokens.ToList();
                    nameList.RemoveAt(tokens.Length - 1);
                    nameList.RemoveAt(0);
                    names[1] = nameList.ToArray().Join(" ");
                }
            }

            return names;
        }

        /// <summary>
        /// Removes trailling occurrence of the specified string from the current string object.
        /// </summary>
        /// <param name="instance">current string object</param>
        /// <param name="trimString">text to remove</param>
        /// <returns>the trimmed string/text</returns>
        public static string TrimEnd(this string instance, string trimString)
        {
            string result = instance;
            if (result.EndsWith(trimString))
            {
                result = result.Substring(0, result.Length - trimString.Length);
            }

            return result;
        }

        /// <summary>
        /// Removes extra spaces before, after, and in between.
        /// </summary>
        /// <param name="text">text to trim</param>
        /// <returns>desired text</returns>
        public static string TrimExtraSpaces(this string text)
        {
            if (String.IsNullOrEmpty(text))
            {
                return String.Empty;
            }

            return String.Join(" ",
                text
                .Trim()
                .Split(new char[] { ' ' })
                .AsEnumerable()
                .Where(d => !String.IsNullOrEmpty(d))
                .ToList()
            );
        }

        /// <summary>
        /// Returns empty string if text is null and removes white spaces.
        /// </summary>
        /// <param name="text">text to trim</param>
        /// <returns>desired trimmed text or empty string</returns>
        public static string TrimOrEmpty(this string text)
        {
            if (String.IsNullOrEmpty(text))
            {
                return String.Empty;
            }

            return text.Trim();
        }

        /// <summary>
        /// Removes leading occurrence of the specified string from the current string object.
        /// </summary>
        /// <param name="instance">current string object</param>
        /// <param name="trimString">text to remove</param>
        /// <returns>the trimmed string/text</returns>
        public static string TrimStart(this string instance, string trimString)
        {
            string result = instance;
            if (result.StartsWith(trimString))
            {
                result = result.Substring(trimString.Length);
            }

            return result;
        }

        #endregion
    }
}
